﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using Db4objects.Db4o.Linq;
using FlickrNet;
using Microsoft.Surface.Presentation.Controls;
using ZOIL.DataBackend;
using ZOIL.DataModel;
using ZOIL.Framework.Presentation;
using ZOIL.Library.Behaviors;
using ZOIL.Library.Components;
using ZOIL.Library.Devices;
using ZOIL.Library.Devices.Surface;

namespace Sample.PhotoBrowser
{
    public partial class Window1 : SurfaceWindow, INotifyPropertyChanged
    {
        #region private fields

        private ZLandscapeDefaultSurfaceHandler _landscapeSurfaceHandler;

        private ZLandscapeDefaultMouseHandler _landscapeMouseHandler;

        private BackgroundWorker _searchByWordWorker;

        private const string _flickrApiKey = "c705bfbf75e8d40f584c8a946cf0834c";

        private Flickr _flickr;

        private RootCollection _rootCollection;

        private RootCollectionView _rootCollectionView;

        private ZInformationLandscape _landscape;

        private ZLandscapeCanvas _landscapeCanvas;

        #endregion

        #region public properties

        #region ElementCount

        private int _elementCount;

        /// <summary>
        /// Gets or sets the ElementCount property. This observable property 
        /// indicates ....
        /// </summary>
        public int ElementCount
        {
            get { return _elementCount; }
            set
            {
                if (_elementCount != value)
                {
                    int old = _elementCount;
                    _elementCount = value;
                    OnElementCountChanged(old, value);
                    RaisePropertyChanged("ElementCount");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the ElementCount property.
        /// </summary>
        protected virtual void OnElementCountChanged(int oldElementCount, int newElementCount)
        {
        }

        #endregion

        #region IsSearchActive

        private bool _isSearchActive;

        /// <summary>
        /// Gets or sets the IsSearchActive property. This observable property 
        /// indicates ....
        /// </summary>
        public bool IsSearchActive
        {
            get { return _isSearchActive; }
            set
            {
                if (_isSearchActive != value)
                {
                    bool old = _isSearchActive;
                    _isSearchActive = value;
                    OnIsSearchActiveChanged(old, value);
                    RaisePropertyChanged("IsSearchActive");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the IsSearchActive property.
        /// </summary>
        protected virtual void OnIsSearchActiveChanged(bool oldIsSearchActive, bool newIsSearchActive)
        {
        }

        #endregion

        #region SearchProgress

        private int _searchProgress;

        /// <summary>
        /// Gets or sets the SearchProgress property. This observable property 
        /// indicates
        /// </summary>
        public int SearchProgress
        {
            get { return _searchProgress; }
            set
            {
                if (_searchProgress != value)
                {
                    int old = _searchProgress;
                    _searchProgress = value;
                    OnSearchProgressChanged(old, value);
                    RaisePropertyChanged("SearchProgress");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the SearchProgress property.
        /// </summary>
        protected virtual void OnSearchProgressChanged(int oldSearchProgress, int newSearchProgress)
        {
        }

        #endregion

        #region SearchText

        private String _searchText;

        /// <summary>
        /// Gets or sets the SearchText property. This observable property 
        /// indicates
        /// </summary>
        public String SearchText
        {
            get { return _searchText; }
            set
            {
                if (_searchText != value)
                {
                    String old = _searchText;
                    _searchText = value;
                    OnSearchTextChanged(old, value);
                    RaisePropertyChanged("SearchText");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the SearchText property.
        /// </summary>
        protected virtual void OnSearchTextChanged(String oldSearchText, String newSearchText)
        {
        }

        #endregion

        #region DynamicQueryText

        private String _dynamicQueryText;

        /// <summary>
        /// Gets or sets the DynamicQueryText property. This observable property 
        /// indicates
        /// </summary>
        public String DynamicQueryText
        {
            get { return _dynamicQueryText; }
            set
            {
                if (_dynamicQueryText != value)
                {
                    String old = _dynamicQueryText;
                    _dynamicQueryText = value;
                    OnDynamicQueryTextChanged(old, value);
                    RaisePropertyChanged("DynamicQueryText");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the DynamicQueryText property.
        /// </summary>
        protected virtual void OnDynamicQueryTextChanged(String oldDynamicQueryText, String newDynamicQueryText)
        {
            dynamicQueryOnRootCollection(newDynamicQueryText);
        }

        #endregion

        #region IsSelectionActive

        private bool _isSelectionActive;

        /// <summary>
        /// Gets or sets the IsSelectionActive property. This observable property 
        /// indicates
        /// </summary>
        public bool IsSelectionActive
        {
            get { return _isSelectionActive; }
            set
            {
                if (_isSelectionActive != value)
                {
                    bool old = _isSelectionActive;
                    _isSelectionActive = value;
                    OnIsSelectionActiveChanged(old, value);
                    RaisePropertyChanged("IsSelectionActive");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the IsSelectionActive property.
        /// </summary>
        protected virtual void OnIsSelectionActiveChanged(bool oldIsSelectionActive, bool newIsSelectionActive)
        {
            if (newIsSelectionActive)
            {
                _landscapeMouseHandler.IsEnabled = false;
                _landscapeSurfaceHandler.IsEnabled = false;
            }
            else
            {
                _landscapeMouseHandler.IsEnabled = true;
                _landscapeSurfaceHandler.IsEnabled = true;
            }
                
        }

        #endregion

        #endregion

        #region ctor

        public Window1()
        {
            InitializeComponent();

            this.Loaded += OnLoaded;
        }

        #endregion

        #region private methods

        private RootCollection loadRootCollection()
        {
            var rootCollections = from RootCollection rc in Database.getInstance().Objects
                                  select rc;

            try
            {
                return rootCollections.Single();
            }
            catch
            {
                RootCollection temp = new RootCollection();
                Database.getInstance().StoreItem(temp);
                return temp;
            }
        }

        private void loadPictures(object sender, DoWorkEventArgs e)
        {
            PhotoSearchOptions searchOptions = new PhotoSearchOptions();
            searchOptions.Tags = e.Argument as string;
            PhotoCollection allPhotos = _flickr.PhotosSearch(searchOptions);

            int currentPhoto = 1;

            String randomDescription = "No description found.";

            Random rnd = new Random();


            foreach (Photo photo in allPhotos)
            {
                if ((sender as BackgroundWorker).CancellationPending == true)
                {
                    e.Cancel = true;
                    return;
                }

                ZPhoto zp = new ZPhoto();

                zp.DateUploaded = photo.DateUploaded;
                zp.PhotoId = photo.PhotoId;

                Person person = _flickr.PeopleGetInfo(photo.UserId);

                zp.UserName = person.UserName;
                zp.RealName = person.RealName;
                zp.UserCountry = person.Location;
                zp.Title = photo.Title;
                zp.SmallUrl = new Uri(photo.SmallUrl);
                zp.Views = photo.Views.HasValue ? photo.Views.Value : 0;
                zp.BuddyIconURL = new Uri(person.BuddyIconUrl);
                zp.Tags = new List<string>();

                if (!String.IsNullOrEmpty(photo.Description))
                    zp.Description = photo.Description;
                else 
                    zp.Description = randomDescription;

                Collection<PhotoInfoTag> tagCollection = _flickr.TagsGetListPhoto(photo.PhotoId);

                foreach (PhotoInfoTag tag in tagCollection)
                {
                    zp.Tags.Add(tag.TagText);
                }

                VisualProperties vsp = VisualProperties.Default();
                vsp.X = (rnd.NextDouble() * (this.ActualWidth)) + (1024) - (this.ActualWidth / 2);
                vsp.Y = (rnd.NextDouble() * (this.ActualHeight)) + (768) - (this.ActualHeight / 2);

                Dispatcher.Invoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate { _rootCollection.Add(zp, vsp); }, null);

                int percentage = (int)(((double)currentPhoto / (double)allPhotos.Count) * 100);

                (sender as BackgroundWorker).ReportProgress(percentage);
                
                currentPhoto++;
            }
        }

        private void searchByWord()
        {
            if (!String.IsNullOrEmpty(SearchText))
            {
                _rootCollection.Clear();
                IsSearchActive = true;
                _searchByWordWorker.RunWorkerAsync(SearchText);
            }
        }

        private void dynamicQueryOnRootCollection(string word)
        {
            foreach (ZPhoto photo in _rootCollection.OfType<ZPhoto>())
            {
                IVisualProperties props = _rootCollection.GetVisualProperties(photo.ID).First();

                if (photo.Tags.Any(tag => tag.ToLower().Contains(word.ToLower())))
                {
                    props.Opacity = 1;
                    props.Width = props.Height = 100;
                }
                else
                {
                    props.Opacity = 0.5;
                    props.Width = props.Height = 50;
                }
            }
        }

        private void initSearchByWordWorker()
        {
            _searchByWordWorker = new BackgroundWorker();
            _searchByWordWorker.WorkerReportsProgress = true;
            _searchByWordWorker.DoWork += new DoWorkEventHandler(loadPictures);
            _searchByWordWorker.ProgressChanged += new ProgressChangedEventHandler(OnSearchByWordWorkerProgressChanged);
            _searchByWordWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnSearchByWordWorkerCompleted);
        }

        #endregion

        #region event handling

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            _flickr = new Flickr(_flickrApiKey);

            _rootCollection = loadRootCollection();

            _rootCollectionView = MVVMUtil.CreateDefaultView(_rootCollection) as RootCollectionView;
            _rootCollectionView.Width = 2048;
            _rootCollectionView.Height = 1526;

            _rootCollection.CollectionChanged += OnRootCollectionChanged;

            _landscape = _rootCollectionView.Landscape;
            _landscape.ConstraintsEnabled = true;

            _landscapeCanvas = _landscape.Content as ZLandscapeCanvas;
            Interaction.GetBehaviors(_landscapeCanvas).Add(new ConnectionBehavior());

            ElementCount = _rootCollection.Count;

            _landscapeMouseHandler = new ZLandscapeDefaultMouseHandler(_rootCollectionView.Landscape);
            _landscapeSurfaceHandler = new ZLandscapeDefaultSurfaceHandler(_rootCollectionView.Landscape);

            RootGrid.Children.Add(_rootCollectionView);
            Grid.SetRow(_rootCollectionView, 1);

            initSearchByWordWorker();
        }

        private void OnRootCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            ElementCount = _rootCollection.Count;
        }

        private void OnSearchByWordWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            IsSearchActive = false;
        }

        private void OnSearchByWordWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate { SearchProgress = e.ProgressPercentage; }, null);
        }

        private void OnSearchButtonClick(object sender, RoutedEventArgs e)
        {
            searchByWord();
        }

        private void OnSearchTextBoxKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Return)
            {
                searchByWord();
            }
        }

        private void OnSelectionBehaviorSelectionFinished(object sender, SelectionEventArgs e)
        {
            IsSelectionActive = false;

            ZPortal portal = new ZPortal();
            ZPortalShape portalShape = new ZPortalShape();

            LinkManager.Link(portal, portalShape);

            // Set Geometry from PortalShape to the Selectionshape
            Geometry geo = e.Selection.SelectionGeometry;

            // Apply Landscapetransformations on the Selectiongeometry to avoid wrong size and position of the PortalShape
            geo.Transform = _landscape.InverseLandscapeTransform;
            portalShape.ShapeGeometry = geo;

            // Set HitTestType from PortalShape to ZPhotoView, that the shape recognizes only Photos from Flickr
            portalShape.HitTestType = typeof(ZPhotoView);

            // Connect the Portal with the Portalshape
            portal.PortalShape = portalShape;

            // Apply VisualProperties (including a Template) to our Portal and our Portalshape
            VisualProperties vp = VisualProperties.Default();
            vp.TemplateId = "PortalShapeTemplate";
            vp.X = geo.Bounds.X;
            vp.Y = geo.Bounds.Y;
            vp.ZIndex = 100000000;

            VisualProperties hsvp = VisualProperties.Default();
            hsvp.TemplateId = "PortalTemplate";
            hsvp.Width = 496;
            hsvp.Height = 351;
            hsvp.ZIndex = 100000000;

            // Add both (PortalShape + Portal) into our Landscape
            _rootCollection.Add(portalShape, vp);
            _rootCollection.Add(portal, hsvp);
            
            // Apply a manual Hittest on the Portalshape
            portalShape.InvalidateShape();
        }

        private void OnClearLandscapeClick(object sender, RoutedEventArgs e)
        {
            _rootCollection.Clear();
        }

        private void OnSortClick(object sender, RoutedEventArgs e)
        {
            int x = (int)Math.Sqrt(_rootCollection.OfType<ZPhoto>().Count()) + 1;
            int currentPhoto = 0;

            List<ZPhotoView> photos = _rootCollectionView.LandscapeCanvas.Children.OfType<ZPhotoView>().ToList<ZPhotoView>();

            for (int i = 0; i <= x * 120; i += 120)
            {
                for (int j = 0; j <= x * 120; j += 120)
                {
                    if (currentPhoto < photos.Count)
                    {
                        ZPhotoViewModel zpvm = (ZPhotoViewModel)photos[currentPhoto].DataContext;
                        ZPhoto zp = zpvm.Model;
                        IVisualProperties props = _rootCollection.GetVisualProperties(zp.ID).First();

                        TransformGroup tg = new TransformGroup();

                        #region Animate X
                        DoubleAnimation da = new DoubleAnimation(0, j + (_landscape.ActualWidth / 2) - (x * 120 / 2) - props.X, new Duration(new TimeSpan(0, 0, 0, 1)));
                        TranslateTransform tt = new TranslateTransform();
                        tg.Children.Add(tt);
                        tt.BeginAnimation(TranslateTransform.XProperty, da);
                        //props.Y = i + (_landscape.ActualHeight / 2) - (x * 120 / 2);
                        #endregion

                        #region Animate Y
                        DoubleAnimation da2 = new DoubleAnimation(0, i + (_landscape.ActualHeight / 2) - (x * 120 / 2) - props.Y, new Duration(new TimeSpan(0, 0, 0, 1)));
                        TranslateTransform tt2 = new TranslateTransform();
                        tg.Children.Add(tt2);
                        tt2.BeginAnimation(TranslateTransform.YProperty, da2);
                        photos[currentPhoto].RenderTransform = tg;
                        //props.Y = i + (_landscape.ActualHeight / 2) - (x * 120 / 2);
                        #endregion

                        currentPhoto++;
                    }
                }
            }

        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(name));
        }

        #endregion
    }
}
